using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Net;
using System.Net.Mail;
using System.Text;
using System.Timers;
using System.Xml;
using Workflows.Components.DALC;

namespace Workflows.Components
{
    /// <summary></summary>
    public delegate void WorkflowServiceError(string message);

    /// <summary></summary>
    public class WorkflowService
    {
        #region fields
        private bool _IsActive;

        private WorkflowEngine _Engine;
        private Dictionary<Guid, WorkflowDesign> _AllWorkflowDesigns;
        
        // settings 
        private Timer _WorkflowRetrievalTimer;
        private Timer _WorkflowDispatcherTimer;
        private int _DispatcherTimeInterval = 1000*30;  // 30 sec
        private int _RetrievalTimeInterval = 1000*60*5; // 5 min
        private EventLog _EventLog;
        private string _LogName="WORKFLOW ENGINE";
        private string _LogSource = "Workflow";
        #endregion

        #region public
        /// <summary></summary>
        public WorkflowService()
        {
            this._Engine=new WorkflowEngine();
            this._Engine.OnWorkflowTriggerFired += new WorkflowTriggerFired(WorkflowEngine_OnWorkflowTriggerFired);
            this._RetrievalTimeInterval = int.Parse(ConfigurationManager.AppSettings.Get("WorkflowRetrievalInterval"));
            this._DispatcherTimeInterval = int.Parse(ConfigurationManager.AppSettings.Get("WorkflowDispatcherInterval"));
            this._LogName = ConfigurationManager.AppSettings.Get("EventLogName");
            this._LogSource = ConfigurationManager.AppSettings.Get("EventLogSource");
            this.RegisterEventLog();
            this._AllWorkflowDesigns = new Dictionary<Guid, WorkflowDesign>();
        }

        private void RegisterEventLog()
        {
            if(!EventLog.SourceExists(this._LogSource))
            {
                EventLog.CreateEventSource(this._LogSource,this._LogName);
            }
            this._EventLog=new EventLog(this._LogName,".",this._LogSource);
            this._EventLog.MaximumKilobytes = 4096;
            this._EventLog.ModifyOverflowPolicy(OverflowAction.OverwriteAsNeeded, 7);
        }

        /// <summary></summary>
        public void Start()
        {
            this.StartRetrieval();
            this.StartDispatcher();
            this._IsActive = true;
        }

        /// <summary></summary>
        public void Stop()
        {
            this.StopRetrieval();
            this.StopDispatcher();
            this._IsActive = false;
        }

        /// <summary></summary>
        public bool IsActive
        {
            get
            {
                return this._IsActive;
            }
        }
        #endregion

        #region timer
        private void StartRetrieval()
        {
            if (this._WorkflowRetrievalTimer != null)
            {
                this._WorkflowRetrievalTimer.Dispose();
                this._WorkflowRetrievalTimer = null;
            }
            GC.Collect();
            GC.WaitForFullGCComplete();
            this._WorkflowRetrievalTimer = new Timer(this._RetrievalTimeInterval);
            this._WorkflowRetrievalTimer.Enabled = true;
            this._WorkflowRetrievalTimer.Elapsed += new ElapsedEventHandler(SynchronizeWorkflowsFromStore);
            this._WorkflowRetrievalTimer.Start();
        }

        private void StartDispatcher()
        {
            if(this._WorkflowDispatcherTimer!=null)
            {
                this._WorkflowDispatcherTimer.Dispose();
                this._WorkflowDispatcherTimer = null;
            }
            GC.Collect();
            GC.WaitForFullGCComplete();
            this._WorkflowDispatcherTimer = new Timer(this._DispatcherTimeInterval);
            this._WorkflowDispatcherTimer.Enabled = true;
            this._WorkflowDispatcherTimer.Elapsed += new ElapsedEventHandler(DispatchWorkflows);
            this._WorkflowDispatcherTimer.Start();
        }

        private void StopRetrieval()
        {
            this._WorkflowRetrievalTimer.Stop();
        }

        private void StopDispatcher()
        {
            this._WorkflowDispatcherTimer.Stop();
        }

        #endregion

        #region retrieval
        void SynchronizeWorkflowsFromStore(object sender, ElapsedEventArgs e)
        {
            this.StopRetrieval();
            //this.StopDispatcher();
            lock(this._AllWorkflowDesigns)
            {
                List<WorkflowDesign> wfDesigns = WorkflowDesignDALC.GetAllWorkflowDesigns();
                this._AllWorkflowDesigns = new Dictionary<Guid, WorkflowDesign>();
                if(wfDesigns !=null && wfDesigns.Count>0)
                {
                    foreach(WorkflowDesign wfDesign in wfDesigns)
                    {
                        if (this._Engine.CanWorkflowStart(wfDesign))
                        {
                            // WorkflowInstance wfInstance = this._Engine.CreateNewWorkflowInstance(wfDesign);
                            this._AllWorkflowDesigns.Add(wfDesign.WorkflowGuid, wfDesign);
                        }
                    }
                }
                
            }
            this.StartRetrieval();
            //this.StartDispatcher();
        }
        #endregion

        #region dispatcher
        void DispatchWorkflows(object sender, ElapsedEventArgs e)
        {
            this.StopDispatcher();
            lock (this._AllWorkflowDesigns)
            {
                try
                {
                    if (this._AllWorkflowDesigns != null && this._AllWorkflowDesigns.Count > 0)
                    {
                        foreach (WorkflowDesign wfDesign in this._AllWorkflowDesigns.Values)
                        {
                            try
                            {
                                this._Engine.ProcessWorkflowTrigger(wfDesign);
                            }
                            catch (Exception wfServiceException)
                            {
                                this.LogErrorMessage("Error occurred in while processing workflow trigger: " + wfServiceException.ToString());
                                this.LogErrorMessage(wfServiceException.StackTrace);
                            }
                        }
                    }
                }
                catch(Exception ex)
                {
                    this.LogErrorMessage("Error while dispatching workflow: "+ex.ToString());
                    this.LogErrorMessage(ex.StackTrace);
                }
            }
            this.StartDispatcher();
        }
        #endregion

        #region create run and jobs
        void WorkflowEngine_OnWorkflowTriggerFired(
            Run run, WorkflowTriggerInstance triggerInstance,  
            List<WorkflowComponentBase> wfComponents, 
            List<Connector> wfConnectors)
        {
            int jobCountForRun = 0;
            Dictionary<Guid, WorkflowComponentBase> enabledComponents =
                this.UpdateDependencies(wfComponents, wfConnectors);

            foreach (Guid componentID in enabledComponents.Keys)
            {
                WorkflowComponentBase wfComponent = enabledComponents[componentID];
                bool isFirstComponentInWorkflow =
                    this._Engine.IsComponentFirstInWorkflow(
                        wfComponent, triggerInstance.TriggerComponentID, wfConnectors);
                bool isLastComponentInWorkflow =
                    this._Engine.IsComponentLastInWorkflow(wfComponent, wfComponents, wfConnectors);
                this._Engine.CreateJob(
                    run.RunID, wfComponent, wfComponent.Dependency,
                    isFirstComponentInWorkflow,
                    isLastComponentInWorkflow);
                jobCountForRun++;
            }
            if(jobCountForRun>0)
            {
                string info = string.Format("Created {0} jobs for run {1}", jobCountForRun, run.RunID.ToString());
                this.LogInfo(info);
            }
        }
        #endregion

        #region dependencies update
        private Dictionary<Guid, WorkflowComponentBase> UpdateDependencies(
            List<WorkflowComponentBase> wfComponents,
            List<Connector> wfConnectors)
        {
            Dictionary<Guid, WorkflowComponentBase> enabledComponents = new Dictionary<Guid, WorkflowComponentBase>();
            if (wfComponents != null && wfComponents.Count > 0)
            {
                foreach (WorkflowComponentBase wfComponent in wfComponents)
                {
                    if (wfComponent.Enabled)
                    {
                        enabledComponents.Add(wfComponent.ComponentID, wfComponent);
                    }
                }
            }
            Dictionary<Guid, List<Guid>> depencendiesByComponent = new Dictionary<Guid, List<Guid>>();
            if (wfConnectors!=null && wfConnectors.Count>0)
            {
                foreach (Connector connector in wfConnectors)
                {
                    if (depencendiesByComponent.ContainsKey(connector.ToComponentID))
                    {
                        List<Guid> dependencies = depencendiesByComponent[connector.ToComponentID];
                        if (!dependencies.Contains(connector.FromComponentID))
                            dependencies.Add(connector.FromComponentID);
                        depencendiesByComponent[connector.ToComponentID] = dependencies;
                    }
                    else
                    {
                        List<Guid> dependencies = new List<Guid>();
                        dependencies.Add(connector.FromComponentID);
                        depencendiesByComponent.Add(connector.ToComponentID, dependencies);
                    }
                }
            }

            Guid[] componentIDs = new Guid[enabledComponents.Count];
            enabledComponents.Keys.CopyTo(componentIDs, 0);
            for (int i = 0; i < componentIDs.Length; i++)
            {
                Guid componentID = componentIDs[i];
                List<Guid> dependencies = new List<Guid>();
                GetUpstreamEnabledComponent(
                    componentID, enabledComponents, depencendiesByComponent, ref dependencies);
                enabledComponents[componentID].Dependency = dependencies;
            }

            return enabledComponents;
        }

        private void GetUpstreamEnabledComponent(
            Guid currentComponentID,
            Dictionary<Guid, WorkflowComponentBase> enabledComponents,
            Dictionary<Guid, List<Guid>> dependencies,
            ref List<Guid> upstreamEnabledComponentIDs)
        {
            if (dependencies.ContainsKey(currentComponentID))
            {
                List<Guid> upstreamComponentIDs = dependencies[currentComponentID];
                foreach (Guid upstreamComponentID in upstreamComponentIDs)
                {
                    if (enabledComponents.ContainsKey(upstreamComponentID))
                    {
                        if (!upstreamEnabledComponentIDs.Contains(upstreamComponentID))
                        {
                            upstreamEnabledComponentIDs.Add(upstreamComponentID);
                        }
                    }
                    else
                    {
                        if (dependencies.ContainsKey(upstreamComponentID))
                        {
                            GetUpstreamEnabledComponent(
                                upstreamComponentID, enabledComponents,
                                dependencies, ref upstreamEnabledComponentIDs);
                        }
                    }
                }
            }
        }
        #endregion

        #region notification
        private void LogErrorMessage(string msg)
        {
            this._EventLog.WriteEntry(msg,EventLogEntryType.Error);
            string subj = "Workflow service error";
            string body = msg;
            string src = "Workflow Service";
            this.SendNotification(body, subj, src);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="msg"></param>
        private void LogInfo(string msg)
        {
            this._EventLog.WriteEntry(msg,EventLogEntryType.Information);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="subj"></param>
        /// <param name="source"></param>
        private void SendNotification(string msg, string subj, string source)
        {
            try
            {
                string[] sendToEmailAddr =
                    ConfigurationManager.AppSettings.Get("SendToEmailAddress").Split(new char[] {';', ','});
                bool requireAuthentication = bool.Parse(ConfigurationManager.AppSettings.Get("RequireAuthentication"));
                bool enableSSL = bool.Parse(ConfigurationManager.AppSettings.Get("EnableSSL"));
                string smtpServerName = ConfigurationManager.AppSettings.Get("SmtpServer");
                MailMessage mail=new MailMessage();
                mail.From = new MailAddress("WorkflowService@" + Environment.UserDomainName + ".com");
                mail.Subject = "<b>" + subj + "</b>";
                foreach(string toAddr in sendToEmailAddr)
                {
                    mail.To.Add(new MailAddress(toAddr));
                }
                StringBuilder buffer=new StringBuilder();
                buffer.Append("<b>" + source + "</b>");
                buffer.Append("<hr/>");
                buffer.Append(msg);
                mail.Body = buffer.ToString();
                mail.IsBodyHtml = true;
                mail.Priority = MailPriority.High;
                SmtpClient mailClient=new SmtpClient(smtpServerName);
                if(requireAuthentication)
                {
                    string userName = ConfigurationManager.AppSettings.Get("EmailUserName");
                    string password = ConfigurationManager.AppSettings.Get("EmailPassword");
                    mailClient.Credentials = new NetworkCredential(userName, password);
                    mailClient.EnableSsl = enableSSL;
                }
                mailClient.Send(mail);
            }
            catch(Exception ex)
            {
                this.LogErrorMessage(ex.ToString());
                this.LogErrorMessage(ex.StackTrace);
            }
        }
        #endregion
    }
}
